package com.ratta.supernote.note.plugincore.utils;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.os.PowerManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.Log;

import androidx.core.content.ContextCompat;


import com.blankj.molihuan.utilcode.util.LogUtils;
import com.ratta.supernote.note.plugincore.bean.ExternalStorageBean;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;

public class SystemUtils {

    public static final String USBDisk_Path = Environment.getExternalStorageDirectory() + File.separator;

    private static final String TAG = "SystemUtils";
    private static volatile SystemUtils INSTANCE;
    private static HashSet<String> statusBarAndSlideBarLocksSet = new HashSet<>();
    private static HashMap<String, PowerManager.WakeLock> wakeLockHashMap = new HashMap<>();
    // Tasks that don't timeout
    private static String[] acquireTag = {"Supernote_export_note", "Supernote noteQrcode", "Supernote set style", "Supernote pageOperation", "Supernote noteUpdate"};
    private static long TIME_OUT = 1000 * 60 * 5;


    /**
     * 管理订阅，确保任务可取消。
     */
    private static final CompositeDisposable compositeDisposable = new CompositeDisposable();

    private static Disposable slideBarDisposable;

    private SystemUtils() {

    }

    public static SystemUtils getInstance() {
        if (INSTANCE == null) {
            synchronized (SystemUtils.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SystemUtils();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * Prevent true sleep for recognition
     * @param context
     * @param isStop true to disable, false to enable
     */
    @SuppressLint("InvalidWakeLockTag")
    public static void setRecognitionWake(Context context, boolean isStop, String tag) {
        Log.i(TAG, "setRecognitionWake");
        setSystemDormancyState(context, isStop, tag);
    }

    /**
     * @param context
     * @param isStop true to disable, false to enable
     */
    @SuppressLint("InvalidWakeLockTag")
    public static void setConvertWake(Context context, boolean isStop, String tag) {
        Log.i(TAG, "setConvertWake");
        setSystemDormancyState(context, isStop, tag);
    }

    @SuppressLint("InvalidWakeLockTag")
    public static void sendAutoCloseWake(Context context, String tag) {
        // Prevent true sleep
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag); // Second parameter is function name, don't duplicate
        wakeLock.acquire(120000);
    }

    /**
     * @param context
     * @param isStop true to disable, false to enable
     */
    @SuppressLint("InvalidWakeLockTag")
    public static void setAutoRecognitionWake(Context context, boolean isStop, String tag) {
        Log.i(TAG, "setAutoRecognitionWake");
        setSystemDormancyState(context, isStop, tag);
    }


    /**
     * @param context
     * @param isForbidden true to disable, false to enable
     */
    @SuppressLint("InvalidWakeLockTag")
    public static void setStatusBarAndSlideBarAndSleepState(Context context, boolean isForbidden, String tag) {
        Log.i(TAG, "setStatusBarAndSlideBarAndSleepState: get lock slideBar statusBar " + isForbidden + " WakeLockTag " + tag);
        setStatusBarAndSlideBarState(context, isForbidden, tag);
        setSystemDormancyState(context, isForbidden, tag);
    }

    public static void setStatusBarAndSlideBarState(Context context, boolean isLock, String tag) {
        Log.i(TAG, "getStatusBarAndSlideBarState: sendBroadcast lock slidebar statusbar " + isLock + " " + tag);
        boolean isForbidden = false;
        if (isLock) {
            // Add all items that need to be locked
            statusBarAndSlideBarLocksSet.add(tag);
            Log.i(TAG, "statusBarAndSlideBarLocksSet: " + statusBarAndSlideBarLocksSet);
            isForbidden = true;
        } else {
            // Check if count is 0 after removing tag, don't unlock if not 0
            boolean remove = statusBarAndSlideBarLocksSet.remove(tag);
            if (statusBarAndSlideBarLocksSet.size() != 0 || !remove) {
                Log.i(TAG, "statusBarAndSlideBarLocksSet: " + tag + " can not cancel lock " + statusBarAndSlideBarLocksSet);
                if (statusBarAndSlideBarLocksSet.size() != 0) {
                    isForbidden = true;
                } else {
                    return;
                }
            }
        }

        // Disable pull-down and sidebar
        Intent intent = new Intent();
        // Set broadcast name (set Action)
        intent.setAction("com.ratta.supernote.launcher.BroadcastReceiver.slidebarstatusbar");
        // Carry data
        intent.putExtra("lockStatusbar", isForbidden);
        intent.putExtra("lockSlidebar", isForbidden);
        intent.putExtra("packageName", context.getPackageName());
        // Send broadcast (unordered broadcast)
        context.sendBroadcast(intent);
        Log.i(TAG, "setStatusBarAndSlideBarState: sendBroadcast lock slidebar statusbar " + isForbidden + " " + tag);
        if(slideBarDisposable != null && !slideBarDisposable.isDisposed()) {
            slideBarDisposable.isDisposed();
        }
        // 做一个定时操作，解锁滑条锁，防止打不开
        if(isLock) {
            slideBarDisposable = Observable.timer(5, TimeUnit.MINUTES)
                    .subscribeOn(Schedulers.io())
                    .subscribe(new Consumer<Long>() {
                        @Override
                        public void accept(Long aLong) throws Throwable {
                            setStatusBarAndSlideBarState(context, false, tag);

                        }
                    });
            compositeDisposable.add(slideBarDisposable);
        }
    }

    public static void clearStatusBarAndSlideBarLockSet(Context context) {
        if (statusBarAndSlideBarLocksSet.size() != 0) {
            Log.i(TAG, "clearStatusBarAndSlideBarLockSet: " + statusBarAndSlideBarLocksSet + " clear locks");
            Intent intent = new Intent();
            intent.setAction("com.ratta.supernote.launcher.BroadcastReceiver.slidebarstatusbar");
            intent.putExtra("lockStatusbar", false);
            intent.putExtra("lockSlidebar", false);
            intent.putExtra("packageName", context.getPackageName());
            context.sendBroadcast(intent);
            statusBarAndSlideBarLocksSet.clear();
        }
        if (wakeLockHashMap.size() != 0) {
            // Don't clear tasks temporarily due to possible recognition tasks
            Log.e(TAG, "clearStatusBarAndSlideBarLockSet: " + wakeLockHashMap.keySet());
//            for (String tag: wakeLockHashMap.keySet()) {
//                PowerManager.WakeLock syncWakeLock = wakeLockHashMap.get(tag);
//                if (syncWakeLock != null && syncWakeLock.isHeld()) {
//                    syncWakeLock.release();
//                }
//                Log.e(TAG, "clearStatusBarAndSlideBarLockSet: release " + tag);
//                wakeLockHashMap.remove(tag);
//            }
        }
    }

    @SuppressLint("InvalidWakeLockTag")
    public static void setSystemDormancyState(Context context, boolean isStop, String tag) {
        Log.i(TAG, "setSystemDormancyState: isStop " + isStop + " tag " + tag);
        if (isStop) {
            if (wakeLockHashMap.size() != 0) {
                Log.i(TAG, "setSystemDormancyState: " + wakeLockHashMap.keySet());
            }
            if (wakeLockHashMap.containsKey(tag)) {
                Log.i(TAG, tag + " Wake has open");
                return;
            }
            // Prevent true sleep
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock syncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag); // Second parameter is function name, don't duplicate

            boolean isAddTimeOut = true;
            for (String value : acquireTag) {

                if (value.equalsIgnoreCase(tag)) {
                    isAddTimeOut = false;
                    break;
                }
            }
            if (isAddTimeOut) {
                syncWakeLock.acquire(TIME_OUT);
            } else {
                syncWakeLock.acquire();
            }
            wakeLockHashMap.put(tag, syncWakeLock);
            Log.i(TAG, "setSystemDormancyState: put tag " + tag);
        } else {
            if (wakeLockHashMap.containsKey(tag)) {
                PowerManager.WakeLock syncWakeLock = wakeLockHashMap.get(tag);
                if (syncWakeLock != null && syncWakeLock.isHeld()) {
                    syncWakeLock.release();
                }
                wakeLockHashMap.remove(tag);
                Log.i(TAG, tag + " Wake has release");
                if (wakeLockHashMap.size() != 0) {
                    Log.i(TAG, "setSystemDormancyState: " + wakeLockHashMap.keySet());
                }
            }
        }
    }

    // Add timeout duration
    public static void addTimeOutForSystemDormancy(String tag) {
        if (wakeLockHashMap.containsKey(tag)) {
            PowerManager.WakeLock syncWakeLock = wakeLockHashMap.get(tag);
            Log.i(TAG, "setSystemDormancyState: add time tag " + tag);
            syncWakeLock.acquire(TIME_OUT);
        }
    }

    public static void forceStopPackage(Context context, String pkgName) {
        ActivityManager am = ContextCompat.getSystemService(context, ActivityManager.class);
        Class<ActivityManager> clzActivityManager = ActivityManager.class;
        try {
            Method mForceStopPackage = clzActivityManager.getDeclaredMethod("forceStopPackage", String.class);
            mForceStopPackage.invoke(am, pkgName);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    // Send sync upload broadcast
    public static void sendSyncUpload(Context context, String path) {
        if (path.startsWith(USBDisk_Path)) {
            Intent intent = new Intent();
            intent.putExtra("path", path);
            intent.setAction("com.ratta.supernote.launcher.intent.action.UPLOAD_NOTE");
            context.sendBroadcast(intent);
        } else {
            Log.e(TAG, "sendSyncUpload: cancel path error");
        }
    }

    private static String system_fonts = "/system/etc/fonts.xml";
    private static String systemFontsDir = "/system/fonts/";//System font files path
    private static String defaultFontName = "DroidSansFallbackFull.ttf";

    public static String getSystemFontsPath() {

        if (!new File(systemFontsDir + defaultFontName).exists()) {
            try {
                File file = new File(system_fonts);
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document doc = builder.parse(file);
                Element root = doc.getDocumentElement();
                NodeList nodeList = root.getChildNodes();
                if (nodeList != null) {
                    for (int i = 0; i < nodeList.getLength(); i++) {
                        Node item = nodeList.item(i);
                        if (item.getNodeType() == Node.ELEMENT_NODE) {
                            for (Node node = item.getFirstChild(); node != null; node = node.getNextSibling()) {
                                if (node.getNodeType() == Node.ELEMENT_NODE) {
                                    String font = node.getFirstChild().getNodeValue();
                                    if (font.endsWith("-Regular.ttf") && new File(systemFontsDir + font).exists()) {
                                        Log.i(TAG, "getSystemFontsPath: " + systemFontsDir + font);
                                        return systemFontsDir + font;
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Log.i(TAG, "getSystemFontsPath: " + systemFontsDir + defaultFontName);
        return systemFontsDir + defaultFontName;
    }

    private static final String SEARCH_ENGINE_GOOGLE = "https://www.google.com/search?q=";
    public static final String URL_ENCODING = "UTF-8";
    private static final String URL_ABOUT_BLANK = "about:blank";
    public static final String URL_SCHEME_ABOUT = "about:";
    public static final String URL_SCHEME_MAIL_TO = "mailto:";
    private static final String URL_SCHEME_FILE = "file://";
    private static final String URL_SCHEME_HTTP = "https://";

    private static final String URL_PREFIX_GOOGLE_PLAY = "www.google.com/url?q=";
    private static final String URL_SUFFIX_GOOGLE_PLAY = "&sa";
    private static final String URL_PREFIX_GOOGLE_PLUS = "plus.url.google.com/url?q=";
    private static final String URL_SUFFIX_GOOGLE_PLUS = "&rct";

    public static boolean isURL(String url) {
        if (url == null) {
            return false;
        }

        url = url.toLowerCase(Locale.getDefault());
        if (url.startsWith(URL_ABOUT_BLANK)
                || url.startsWith(URL_SCHEME_MAIL_TO)
                || url.startsWith(URL_SCHEME_FILE)
                || url.startsWith(URL_SCHEME_HTTP)) {
            return true;
        }

        String regex = "^((ftp|http|https|intent)?://)"                      // support scheme
                + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" // ftp的user@
                + "(([0-9]{1,3}\\.){3}[0-9]{1,3}"                            // URL in IP format -> 199.194.52.184
                + "|"                                                        // Allow IP and DOMAIN
                + "([0-9a-z_!~*'()-]+\\.)*"                                  // Domain -> www.
                + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\."                    // Second level domain
                + "[a-z]{2,6})"                                              // First level domain -> .com or .museum
                + "(:[0-9]{1,4})?"                                           // Port -> :80
                + "((/?)|"                                                   // A slash isn't required if there is no file name
                + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
        Pattern pattern = Pattern.compile(regex);
        return pattern.matcher(url).matches();
    }






    /**
     * Get storage volume type
     */
    public List<ExternalStorageBean> getStorageType(Context mContext) {
        // value 0 for OTG, 1 for TF card
        List<ExternalStorageBean> externalStorageBeanList = new ArrayList<>();
        try {
            StorageManager storageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
            // Get storage type (USB drive or SD card)
            final Method getVolumes = storageManager.getClass().getMethod("getVolumes");
            final List<Object> invokeVolumes = (List<Object>) getVolumes.invoke(storageManager);
            final Class<?> volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            final Method getPath = volumeInfoClazz.getMethod("getPath");
            final Method getDisk = volumeInfoClazz.getMethod("getDisk");
            final Class<?> diskInfoClazz = Class.forName("android.os.storage.DiskInfo");
            final Method getDescription = diskInfoClazz.getMethod("getDescription");
            if (invokeVolumes == null) {
                return externalStorageBeanList;
            }
            String sdCardDir = getSDCardDir(mContext);
            for (int i = 0; i < invokeVolumes.size(); i++) {
                final Object volumeInfo = invokeVolumes.get(i);
                File invokePath = (File) getPath.invoke(volumeInfo);
                if (invokePath == null || !invokePath.exists()) {
                    continue;
                }
                LogUtils.d("invokePath:" + invokePath.getPath() + " - sdCardDir: " + sdCardDir);
                if ("/data".equals(invokePath.getPath()) || "/storage/emulated".equals(invokePath.getPath())) {
                    continue;
                }
                Object invokeDiskInfo = getDisk.invoke(volumeInfo);
                String description = (String) getDescription.invoke(invokeDiskInfo);
//                LogUtils.d("volumeInfo.getClass(): " + volumeInfo.getClass());
//                Integer state = (Integer) volumeInfo.getClass().getMethod("getState").invoke(volumeInfo);

//                LogUtils.d( "getPath:" + invokePath.getPath());
//                LogUtils.d( "getDescription:" + description);
//                LogUtils.d("state:  " + state);
                String path = invokePath.getPath();
                Log.d("","getStorageType path: " + path);
                if (sdCardDir != null && invokePath.getPath().equals(sdCardDir)) {
                    //String tfCardName = getTfCardName(mContext);// This method returns OTG name when both OTG and TF card exist
                    String tfCardName = getOTGNameByPath(mContext, path);
                    LogUtils.d("getStorageType tfCardName: " + tfCardName);
                    if (TextUtils.isEmpty(tfCardName)) {
                        tfCardName = new File(path).getName();
                    }
                    externalStorageBeanList.add(new ExternalStorageBean(1, tfCardName, path));
//                    map.put(1,invokePath.getPath());
                } else {
                    String otgName = getOTGNameByPath(mContext, path);
                    LogUtils.d("getStorageType otgNameByPath: " + otgName);
                    if (TextUtils.isEmpty(otgName)) {
                        otgName = new File(path).getName();
                    }
                    externalStorageBeanList.add(new ExternalStorageBean(0, otgName, path));
//                    map.put(0, invokePath.getPath());
                }
            }

        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
                 ClassNotFoundException e) {
            e.printStackTrace();
        }
        return externalStorageBeanList;
    }

    private String getOTGNameByPath(Context context, String path) {
        StorageManager mStorageManager = context.getSystemService(StorageManager.class);
        List<StorageVolume> volumeList = mStorageManager.getStorageVolumes();
        String mPath = null; // Device path
        for (StorageVolume volume : volumeList) {
            if (null != volume && volume.isRemovable()) {
                String label = volume.getDescription(context);      // This is the USB drive name
//                String status = volume.getState();                   // Device mount status, e.g.: mounted, unmounted
//                boolean isEmulated = volume.isEmulated();            // Whether it's internal storage device
//                boolean isRemovable = volume.isRemovable();          // Whether it's removable external storage device
                try {
                    Class myclass = Class.forName(volume.getClass().getName());
                    Method getPath = myclass.getDeclaredMethod("getPath");
                    getPath.setAccessible(true);
                    mPath = (String) getPath.invoke(volume);
                    if (path.equals(mPath)) {
                        return label;
                    }
                } catch (ClassNotFoundException | NoSuchMethodException |
                         InvocationTargetException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return mPath;
    }

    /**
     * Get SD card path for new device
     * @param context
     * @return
     */
    public static String getSDCardDir(Context context) {
        try {
            String sdcardDir = null;
            StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            Class<?> volumeInfoClazz = null;
            Class<?> diskInfoClazz = null;
            diskInfoClazz = Class.forName("android.os.storage.DiskInfo");
            Method isSd = diskInfoClazz.getMethod("isSd");//isUsb、isSd
            volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            Method getType = volumeInfoClazz.getMethod("getType");

            Method getDisk = volumeInfoClazz.getMethod("getDisk");
            Field path = volumeInfoClazz.getDeclaredField("path");
            Method getVolumes = storageManager.getClass().getMethod("getVolumes");
            Method getDescription = volumeInfoClazz.getMethod("getDescription");
            List<Class<?>> result = (List<Class<?>>) getVolumes.invoke(storageManager);
            if (result == null) {
                return null;
            }
            for (int i = 0; i < result.size(); i++) {
                Object volumeInfo = result.get(i);
                int deviceType = 0;
                if (getType != null) {
                    deviceType = (int) getType.invoke(volumeInfo);
                }
//                LogUtils.d( "insert type：" + deviceType);
                if (deviceType == 0) {
                    Object disk = getDisk.invoke(volumeInfo);
                    if (disk != null && isSd != null) {
                        if ((boolean) isSd.invoke(disk)) {
                            sdcardDir = (String) path.get(volumeInfo);
                            String description = (String) getDescription.invoke(volumeInfo);
//                            LogUtils.d( "description:" + description);
                            break;
                        }
                    }
                }
            }
            if (sdcardDir == null) {
//                LogUtils.d( "sdcardDir null");
                return null;
            } else {
//                LogUtils.i( "sdcardDir " + sdcardDir + File.separator);
                return sdcardDir;
            }
        } catch (Exception e) {
//            LogUtils.i("sdcardDir e " + e.getMessage());
            e.printStackTrace();
        }
//        LogUtils.w( "sdcardDir null");
        return null;
    }

}
